package org.kobjects.base64;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Base64
{
  static final char[] charTab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();

  public static String encode(byte[] data)
  {
    return encode(data, 0, data.length, null).toString();
  }

  public static StringBuffer encode(byte[] data, int start, int len, StringBuffer buf)
  {
    int d;
    if (buf == null)
      buf = new StringBuffer(data.length * 3 / 2);

    int end = len - 3;
    int i = start;
    int n = 0;

    while (i <= end) {
      d = (data[i] & 0xFF) << 16 | (data[(i + 1)] & 0xFF) << 8 | data[(i + 2)] & 0xFF;

      buf.append(charTab[(d >> 18 & 0x3F)]);
      buf.append(charTab[(d >> 12 & 0x3F)]);
      buf.append(charTab[(d >> 6 & 0x3F)]);
      buf.append(charTab[(d & 0x3F)]);

      i += 3;

      if (n++ >= 14) {
        n = 0;
        buf.append("\r\n");
      }
    }

    if (i == start + len - 2) {
      d = (data[i] & 0xFF) << 16 | (data[(i + 1)] & 0xFF) << 8;

      buf.append(charTab[(d >> 18 & 0x3F)]);
      buf.append(charTab[(d >> 12 & 0x3F)]);
      buf.append(charTab[(d >> 6 & 0x3F)]);
      buf.append("=");
    }
    else if (i == start + len - 1) {
      d = (data[i] & 0xFF) << 16;

      buf.append(charTab[(d >> 18 & 0x3F)]);
      buf.append(charTab[(d >> 12 & 0x3F)]);
      buf.append("==");
    }

    return buf;
  }

  static int decode(char c) {
    if ((c >= 'A') && (c <= 'Z'))
      return (c - 'A');
    if ((c >= 'a') && (c <= 'z'))
      return (c - 'a' + 26);
    if ((c >= '0') && (c <= '9'))
      return (c - '0' + 26 + 26);

    switch (c)
    {
    case '+':
      return 62;
    case '/':
      return 63;
    case '=':
      return 0;
    }
    throw new RuntimeException("unexpected code: " + c);
  }

  public static byte[] decode(String s)
  {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try {
      decode(s, bos);
    }
    catch (IOException e) {
      throw new RuntimeException();
    }
    return bos.toByteArray();
  }

  public static void decode(String s, OutputStream os) throws IOException
  {
    int i = 0;

    int len = s.length();
    while (true)
    {
      while ((i < len) && (s.charAt(i) <= ' '))
        ++i;

      if (i == len)
        return;

      int tri = (decode(s.charAt(i)) << 18) + (decode(s.charAt(i + 1)) << 12) + (decode(s.charAt(i + 2)) << 6) + decode(s.charAt(i + 3));

      os.write(tri >> 16 & 0xFF);
      if (s.charAt(i + 2) == '=')
        return;
      os.write(tri >> 8 & 0xFF);
      if (s.charAt(i + 3) == '=')
        return;
      os.write(tri & 0xFF);

      i += 4;
    }
  }
}
